/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
 * secPaver is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package builder

import (
	"fmt"
	pb "gitee.com/openeuler/secpaver/api/proto/project"
	"gitee.com/openeuler/secpaver/common/log"
	"gitee.com/openeuler/secpaver/common/utils"
	"gitee.com/openeuler/secpaver/engine/selinux/pkg/secontext"
	"gitee.com/openeuler/secpaver/engine/selinux/proto"
	"github.com/pkg/errors"
)

// SELinux file attribute
type fileSeInfo struct {
	class       secontext.FileClass
	fileContext *secontext.FileContext
	execDomain  string // domain for process
}

func (b *Builder) processFileContext(prjData *pb.ProjectData) error {
	for _, resource := range prjData.GetResources().GetResourceList() {
		// 1. get filepath
		path := resource.GetPath()
		sePath := getSePath(path)

		// 2. parse filetype
		class, err := getFileClassByName(resource.GetType())
		if err != nil {
			return errors.Wrapf(err, "fail to parse filetype of  %s", path)
		}

		// 3. parse selinux attribute of file
		attr := &proto.FileAttr{}
		if err := utils.UnmarshalPbStruct(resource.GetSelinux(), attr); err != nil {
			return errors.Wrapf(err, "fail to parse selinux attr for %s", path)
		}

		// 4. create the seinfo struct
		if _, ok := b.fileSeInfo[path]; ok {
			return fmt.Errorf("redefine path %s in resourceList", path)
		}

		info := &fileSeInfo{
			class:      class,
			execDomain: attr.GetDomain(),
		}
		b.fileSeInfo[path] = info

		// 4. obtain the file context
		if attr.GetType() != "" { // user specify the file context
			fctx := b.seHandle.GetFileContext(sePath, class)
			if fctx != nil { // the context of same path has been defined in system
				if fctx.Context.Type != attr.GetType() {
					return fmt.Errorf("file context of %s has been defined", sePath)
				} else {
					info.fileContext = fctx
				}
			} else { // create a file context entry
				info.fileContext = &secontext.FileContext{
					Path:    sePath,
					Context: *secontext.CreateDefaultObjectContext(attr.GetType()),
					Class:   class,
					IsLocal: true,
				}

				b.seHandle.AddFileContext(info.fileContext)
			}
		}

		log.Debugf("path: %s, seinfo: %+v", path, info)
	}

	return nil
}

func (b *Builder) processApplicationContext(prjData *pb.ProjectData) error {
	for _, app := range prjData.GetApplicationList().GetApplicationList() {
		// 1. get filepath
		path := app.GetApplication().GetPath()
		sePath := getSePath(path)

		// 2. get or create the file selinux info
		info, ok := b.fileSeInfo[path]
		if ok && info != nil { // the process file has been set in resourceList
			// check the file type
			if info.class != secontext.ComFile && info.class != secontext.UnknownFile {
				return fmt.Errorf("the filetype of %s must be regular file", path)
			}

			info.class = secontext.ComFile
		} else {
			info = &fileSeInfo{
				class: secontext.ComFile,
			}

			b.fileSeInfo[path] = info
		}

		// 3. obtain the file context
		if info.fileContext == nil { // the file context has not been set
			// for process, the file context must be redefined
			if b.seHandle.GetFileContext(sePath, secontext.ComFile) != nil {
				return fmt.Errorf("the file context of %s has been defined", path)
			}

			// auto generate file context
			tp := autoGenFileTypeByPathAndClass(path, secontext.ComFile, true)
			info.fileContext = &secontext.FileContext{
				Path:    sePath,
				Context: *secontext.CreateDefaultObjectContext(tp),
				Class:   secontext.ComFile,
				IsLocal: true,
			}

			b.seHandle.AddFileContext(info.fileContext)
		} else if !info.fileContext.IsLocal {
			return fmt.Errorf("the file context of %s has been defined", path)
		}

		if info.execDomain == "" {
			info.execDomain = getTransProcessType(info.fileContext.Context.Type)
		}

		log.Debugf("application: %s, selinux info: %+v", path, info)
	}

	return nil
}
